﻿#include <iostream>
#include <conio.h>
#include "CustomConsole.h"
using namespace std;

#pragma region 1 控制台初始化
void consoleInit(int w, int h) 
{
    //控制台大小
    setConsoleSize(w, h);
    //隐藏光标
    setCursorVisibility(false);
}
#pragma endregion

#pragma region 2 场景选择相关
enum E_SceneType 
{
    Begin,//开始场景
    Game,//游戏场景
    End,//结束场景
};
#pragma endregion

#pragma region 3 开始场景逻辑相关 and 9 结束场景逻辑相关
//颜色枚举 用来设置颜色 方便阅读
enum E_Color
{
    Blue = 1,
    Green = 2,
    Red = 4,
    White = 7,
};
//开始场景选择索引
enum E_SceneIndex
{
    One,
    Two,
};

void beginOrEndScene(int w, int h, E_SceneType& nowSceneType)
{
    //显示不会一直更新变化的内容
    setCursorPosition(nowSceneType == Begin ? w / 2 - 3 : w / 2 - 4, 8);
    setTextColor(White);
    cout << (nowSceneType == Begin ? "飞行棋" : "游戏结束");

    //表示当前选择的是哪个选项
    E_SceneIndex nowSelIndex = One;
    //是否退出开始场景循环
    bool isQuitBegin = false;
    //开始场景的逻辑循环
    while (true)
    {
        //开始游戏
        setCursorPosition(nowSceneType == Begin ? w / 2 - 4 : w / 2 - 5, 13);
        setTextColor(nowSelIndex == One ? Red : White);
        cout << (nowSceneType == Begin ? "开始游戏" : "回到主菜单");
        //退出游戏
        setCursorPosition(w / 2 - 4, 15);
        setTextColor(nowSelIndex == Two ? Red : White);
        cout << "退出游戏";

        //输入信息的askii码
        int input = _getch();
        switch (input)
        {
            case 'W':
            case 'w':
                //由于这里只有两个选项 所以它可以直接设置类型
                //如果超过两个 就应该像C++入门当中一样去进行+-运算
                //判断当前选中的是哪个索引了
                nowSelIndex = One;
                break;
            case 'S':
            case 's':
                nowSelIndex = Two;
                break;
            case 'J':
            case 'j':
                //选中谁 处理不同的逻辑
                switch (nowSelIndex)
                {
                    case One:
                        //开始游戏 就应该切换场景类型
                        //当选择第一个选项 如果是开始场景 就应该前往游戏场景
                        //否则前往开始场景
                        nowSceneType = nowSceneType == Begin ? Game : Begin;
                        isQuitBegin = true;
                        break;
                    case Two:
                        closeConsole();
                        break;
                }
                break;
        }
        //和while循环配对的break
        //只有当我们选择开始游戏后 才会跳出开始场景循环 
        //来到主循环当中
        if (isQuitBegin)
            break;
    }
}
#pragma endregion

#pragma region 游戏场景逻辑

#pragma region 4 不变的红墙逻辑相关
void drawWall(int w, int h)
{
    //绘制红墙
    //设置为红色
    setTextColor(Red);
    //横向
    for (int i = 0; i < w; i += 2)
    {
        //第一排红墙
        setCursorPosition(i, 0);
        cout << "■";
        //最底部的红墙
        setCursorPosition(i, h - 1);
        cout << "■";
        //另外两行墙体
        setCursorPosition(i, h - 6);
        cout << "■";
        setCursorPosition(i, h - 11);
        cout << "■";
    }
    //竖向
    for (int i = 0; i < h; i++)
    {
        //最左边
        setCursorPosition(0, i);
        cout << "■";
        //最右边
        setCursorPosition(w - 2, i);
        cout << "■";
    }

    //下方提示内容绘制
    setCursorPosition(2, h - 10);
    setTextColor(White);
    cout << "□:普通格子";

    setCursorPosition(2, h - 9);
    setTextColor(Blue);
    cout << "‖:暂停，一回合不动";

    setCursorPosition(26, h - 9);
    setTextColor(Red);
    cout << "●:炸弹，倒退5格";

    setCursorPosition(2, h - 8);
    setTextColor(Red | Green);
    cout << "¤:时空隧道，随机倒退，暂停，换位置";

    setCursorPosition(2, h - 7);
    setTextColor(Red | Blue);
    cout << "★:玩家";
    setCursorPosition(10, h - 7);
    setTextColor(Green | Blue);
    cout << "▲:电脑";
    setCursorPosition(20, h - 7);
    setTextColor(White);
    cout << "◎:玩家和电脑重合";

    setCursorPosition(2, h - 5);
    setTextColor(White);
    cout << "按任意键开始扔色子";
}
#pragma endregion

#pragma region 5 格子枚举和格子结构体相关
//格子的类型
enum E_Grid_Type
{
    Normal,//普通格子
    Boom,//炸弹
    Pause,//暂停
    Tunnel,//时空隧道
};

//位置结构体
struct Vector2
{
    int x;
    int y;

    Vector2()
    {
        x = 0;
        y = 0;
    }

    Vector2(int x, int y) : x(x), y(y){}
};

//格子结构体
struct Grid
{
    E_Grid_Type type;//格子的类型
    Vector2 pos;//格子的位置

    Grid()
    {
        type = Normal;
        pos = {};
    }

    Grid(int x, int y, E_Grid_Type type)
    {
        pos = { x, y };
        this->type = type;
    }

    //根据格子类型和位置 绘制格子
    void draw()
    {
        //设置格子所在的位置 去进行颜色设置会图像绘制
        setCursorPosition(pos.x, pos.y);
        //根据格子的位置 去绘制对应类型的颜色的图像
        switch (type)
        {
            case Normal:
                setTextColor(White);
                cout << "□";
                break;
            case Boom:
                setTextColor(Red);
                cout << "●";
                break;
            case Pause:
                setTextColor(Blue);
                cout << "‖";
                break;
            case Tunnel:
                setTextColor(Red | Green);
                cout << "¤";
                break;
        }
    }
};
#pragma endregion

#pragma region 6 地图结构体相关
struct Map
{
    Grid* grids = nullptr;//格子数组首地址
    int gridNum;//格子数组数量

    //传入 地图的起始位置 以及 有多少个格子
    Map(int x, int y, int num)
    {
        gridNum = num;//记录格子数量
        grids = new Grid[gridNum];//动态在堆上分配格子数组

        //表示x变化的次数
        int indexX = 0;
        //表示y变化的次数
        int indexY = 0;
        //x的步长
        int stepNum = 2;

        //对地图信息进行初始化
        int randomNum;
        for (int i = 0; i < gridNum; i++)
        {
            //初始化每一个格子信息
            //初始化格子类型
            randomNum = getRandom(0, 100);
            //普通格子 85% 或者 这个格子是第一个或者最后一个格子 那么就必须是普通格子
            if(randomNum < 85 || i == 0 || i == gridNum - 1)
                grids[i].type = Normal;
            //炸弹 5%
            else if( randomNum >= 85 && randomNum < 90)
                grids[i].type = Boom;
            //暂停 5%
            else if( randomNum >= 90 && randomNum < 95)
                grids[i].type = Pause;
            //时空隧道 5%
            else
                grids[i].type = Tunnel;

            //初始化格子位置
            grids[i].pos = Vector2(x, y);

            if (indexX == 10)
            {
                //加Y
                y += 1;
                ++indexY;
                //Y变化结束
                if (indexY == 2)
                {
                    //y加了2次过后 就应该进行下一轮变化了
                    indexX = 0;
                    indexY = 0;
                    //步长反向
                    stepNum = -stepNum;
                }
            }
            else
            {
                //X变化
                x += stepNum;
                ++indexX;
            }
            
        }
    }

    ~Map()
    {
        if (grids != nullptr)
        {
            delete[] grids;
            grids = nullptr;
        }
    }

    //绘制地图
    void draw()
    {
        //通过循环命令自己管理的格子们去执行一个绘制的行为
        for (int i = 0; i < gridNum; i++)
        {
            //调用格子数组中每一个格子的绘制方法
            grids[i].draw();
        }
    }
};
#pragma endregion

#pragma region 7 玩家枚举和玩家结构体相关
//玩家电脑枚举
enum class E_PlayerType
{
    Player,//玩家
    Computer,//电脑
};

struct Player
{
    //玩家类型
    E_PlayerType type;
    //当前所在格子位置
    int nowIndex;
    //是否暂停
    bool isPause;

    Player(int index, E_PlayerType type) : nowIndex(index), type(type)
    {
        isPause = false;
    }

    void draw(Map& mapInfo)
    {
        //所在的位置
        //获取到地图信息
        Grid grid = mapInfo.grids[nowIndex];
        setCursorPosition(grid.pos.x, grid.pos.y);
        //根据类型去绘制图像和颜色
        switch (type)
        {
            case E_PlayerType::Player:
                setTextColor(Red | Blue);
                cout << "★";
                break;
            case E_PlayerType::Computer:
                setTextColor(Green | Blue);
                cout << "▲";
                break;
        }
    }
};
#pragma endregion

#pragma region 7 玩家和电脑绘制相关
void drawPlayer(Player& player, Player& computer, Map& map)
{
    //重合时
    if (player.nowIndex == computer.nowIndex)
    {
        //绘制重合时的图像
        //得到位置
        Grid grid = map.grids[player.nowIndex];
        setCursorPosition(grid.pos.x, grid.pos.y);
        setTextColor(White);
        cout << "◎";
    }
    //不重合时
    else
    {
        player.draw(map);
        computer.draw(map);
    }
}
#pragma endregion

#pragma region 8 扔骰子相关逻辑
//擦除提示位置的信息
void clearInfo(int w, int h)
{
    //擦除之前显示的提示信息
    for (int j = 5; j >= 2; j--)
    {
        for (int i = 2; i < w - 2; i++)
        {
            setCursorPosition(i, h - j);
            cout << " ";
        }
    }
}

bool randomMove(int w, int h, Player& player, Player& otherPlayer, Map& map)
{
    //擦除之前显示的提示信息
    clearInfo(w, h);

    //根据扔色子的对象决定提示信息的文本颜色
    setTextColor(player.type == E_PlayerType::Player ? Red | Blue : Green | Blue);

    //暂停相关逻辑
    if (player.isPause)
    {
        //提示暂停相关
        setCursorPosition(2, h - 5);
        cout << "处于暂停状态," << (player.type == E_PlayerType::Player ? "你" : "电脑") << "需要暂停过一回合";
        setCursorPosition(2, h - 4);
        cout << "请按任意键，让" << (player.type == E_PlayerType::Player ? "电脑" : "你") << "开始扔骰子";

        //直接不扔色子 直接返回 
        //提示暂停
        player.isPause = false;
        return false;
    }

    //随机扔骰子
    int randomNum = getRandom(1, 6);
    //让对象移动对应随机扔出的步数
    player.nowIndex += randomNum;

    //提示扔了多少点数
    setCursorPosition(2, h - 5);
    cout << (player.type == E_PlayerType::Player ? "你" : "电脑") << "扔出的点数为：" << randomNum;

    //移动 判断所在格子是什么 处理对应逻辑
    if (player.nowIndex >= map.gridNum - 1)
    {
        //如果超过了终点 就应该拉回来
        player.nowIndex = map.gridNum - 1;
        setCursorPosition(2, h - 4);
        cout << (player.type == E_PlayerType::Player ? "恭喜你，你率先到达了终点" : "很遗憾，电脑率先到达了终点");
        setCursorPosition(2, h - 3);
        cout << "请按任意键结束游戏";
        //应该结束游戏了 到达了终点
        return true;
    }
    else
    {
        //没有到终点，就判断当前对象所在格子是什么格子
        //处理对应逻辑
        Grid grid = map.grids[player.nowIndex];
        switch (grid.type)
        {
            case Normal:
                //不处理任何逻辑
                setCursorPosition(2, h - 4);
                cout << (player.type == E_PlayerType::Player ? "你" : "电脑") << "到达了一个安全位置";
                setCursorPosition(2, h - 3);
                cout << "请按任意键，让" << (player.type == E_PlayerType::Player ? "电脑" : "你") << "开始扔骰子";
                break;
            case Boom:
                player.nowIndex -= 5;
                //遇到炸弹 最多退掉起点 不能比起点还小
                if (player.nowIndex < 0)
                    player.nowIndex = 0;
                setCursorPosition(2, h - 4);
                cout << (player.type == E_PlayerType::Player ? "你" : "电脑") << "踩到了炸弹，退后5格";
                setCursorPosition(2, h - 3);
                cout << "请按任意键，让" << (player.type == E_PlayerType::Player ? "电脑" : "你") << "开始扔骰子";

                break;
            case Pause:
                //暂停一回合
                //必须添加一个暂停标识 才能知道下一回合需要暂停
                //不能扔色子
                player.isPause = true;
                setCursorPosition(2, h - 4);
                cout << (player.type == E_PlayerType::Player ? "你" : "电脑") << "到了暂停点，你需要暂停一回合";
                setCursorPosition(2, h - 3);
                cout << "请按任意键，让" << (player.type == E_PlayerType::Player ? "电脑" : "你") << "开始扔骰子";
                break;
            case Tunnel:
                //随机 退5格   暂停一回合  交换位置
                randomNum = getRandom(1, 3);
                setCursorPosition(2, h - 4);
                cout << (player.type == E_PlayerType::Player ? "你" : "电脑") << "到达了时空隧道";
                setCursorPosition(2, h - 3);
                //退5格
                if (randomNum == 1)
                {
                    player.nowIndex -= 5;
                    if (player.nowIndex < 0)
                        player.nowIndex = 0;
                    cout << "触发了退5格";
                }
                //暂停
                else if (randomNum == 2)
                {
                    player.isPause = true;
                    cout << "触发了暂停一回合";
                }
                //交换位置
                else
                {
                    int temp = player.nowIndex;
                    player.nowIndex = otherPlayer.nowIndex;
                    otherPlayer.nowIndex = temp;
                    cout << "触发了交换位置";
                }
                setCursorPosition(2, h - 2);
                cout << "请按任意键，让" << (player.type == E_PlayerType::Player ? "电脑" : "你") << "开始扔骰子";
                break;
        }
    }
    //默认就没有到达终点
    return false;
}


bool playerRandomMove(int w, int h, Player& player, Player& otherPlayer, Map& map, E_SceneType& nowSceneType)
{
    //玩家扔骰子逻辑
    //检测输入
    _getch();
    //扔骰子的逻辑
    bool isGameOver = randomMove(w, h, player, otherPlayer, map);
    //绘制地图
    map.draw();
    //绘制玩家
    drawPlayer(player, otherPlayer, map);
    //判断是否要结束游戏
    if (isGameOver)
    {
        nowSceneType = End;
        _getch();
    }

    return isGameOver;
}

#pragma endregion

//游戏场景逻辑处理
void gameScene(int w, int h, E_SceneType& nowSceneType)
{
    //绘制不变的内容
    //红墙 文字提示 等等
    drawWall(w, h);

    //绘制地图信息
    Map map = Map(14, 3, 80);
    map.draw();

    //绘制玩家和电脑
    Player player = Player(0, E_PlayerType::Player);
    Player computer = Player(0, E_PlayerType::Computer);
    drawPlayer(player, computer, map);

    while (true)
    {
        //玩家扔骰子逻辑
        if (playerRandomMove(w, h, player, computer, map, nowSceneType))
            break;

        //电脑扔骰子逻辑
        if (playerRandomMove(w, h, computer, player, map, nowSceneType))
            break;
    }
}
#pragma endregion

int main()
{
#pragma region 1 控制台初始化
    int w = 50;
    int h = 30;
    consoleInit(w, h);
#pragma endregion

#pragma region 2 场景选择相关
    E_SceneType nowSceneType = Begin;
    while (true)
    {
        //根据当前场景ID来决定更新哪一个场景的逻辑
        switch (nowSceneType)
        {
            case Begin:
                //开始场景逻辑
                //先把之前的内容清理掉
                system("cls");
                //我们将开始场景逻辑封装到了函数当中
                beginOrEndScene(w, h, nowSceneType);
                break;
            case Game:
                //游戏场景逻辑
                //先把之前的内容清理掉
                system("cls");
                gameScene(w, h, nowSceneType);
                break;
            case End:
                //结束场景逻辑
                //先把之前的内容清理掉
                system("cls");
                beginOrEndScene(w, h, nowSceneType);
                break;
        }
    }
#pragma endregion

}



